package com.ruoyi.aitcommon.utils;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * JAVA并发锁 工具类
 * 使用 ReentrantLock 控制并发
 * 功能：尝试非阻塞地获取锁。如果锁当前没有被其他线程持有，那么当前线程可以获取到锁并返回true；
 * 如果锁已经被其他线程持有，那么当前线程不会被阻塞，而是直接返回false。
 */
public class LockUtils {
	//允许阻塞线程数
	private static final int MAX_LOCKS=3;
	//阻塞等待时间
	private static final int LOCK_TIMES=60;
	//按照关键字缓存锁对象 LockObj
	private static Map<String,LockObj> lockMap=new HashMap<String,LockObj>();
	
	/**
	 * 依据key获取lock管理对象
	 * 每一个key,单独分配一个lockObj
	 * @param key
	 * @return
	 */
	private static LockObj getLock(String key) throws Exception {
		ExceptionUtils.checkBlankOrUndefined(key, "获取锁KEY不能为空");
		LockObj lockObj;
		if(lockMap.containsKey(key)) {
			lockObj=lockMap.get(key);
		}else {
			lockObj=new LockObj();
			lockMap.put(key, lockObj);
		}
		return lockObj;
	}

	/**
	 * 线程并发锁,控制线程间并发执行问题
	 * 同一个锁,只能获取一次,其他进程阻塞等待60S后停止执行
	 * 成功,返回锁
	 * 失败,返回空
	 * 失败情况: 等待超时 or 超过锁上限
	 * 
	 * @param key 锁Key
	 * @return 成功,返回锁对象;失败,返回null
	 */
	public static Lock TryLock(String key) throws Exception {
		if(ObjUtils.isBlank(key)) return null;
		LockObj lockObj = LockUtils.getLock(key);
//		锁申请次数+1
		lockObj.addReqNum();
		if(lockObj.getReqNum()>1){
//			申请次数>1,说明该锁被使用中
//			tryLock方法会等待其他线程释放
			LogUtils.info("存在并发,需要等待!锁信息:Key[{}],ReqNum[{}]", key,lockObj.getReqNum());
		}
		if(lockObj.getReqNum()>MAX_LOCKS) {
//			超过最大锁次数,不用等待,直接获取失败
			LogUtils.error("超过最大并发数,放弃该操作!锁信息:Key[{}],ReqNum[{}]", key,lockObj.getReqNum());
//			获取锁失败,需要释放申请次数,锁申请次数-1
			lockObj.reduceReqNum();
			return null;
		}

//		lockObj尝试获取锁,成功返回true,失败返回false
//		如果锁已经被其他线程持有，当前线程会被阻塞60秒，直到获取到锁为止,超时,获取失败

		boolean tryLock = lockObj.getLock().tryLock(LOCK_TIMES, TimeUnit.SECONDS);
		if(tryLock) {
//			获取成功
			return lockObj.getLock();
		}
		else {
//			获取锁失败,需要释放申请次数,锁申请次数-1
			LogUtils.warn("获取锁失败,释放锁:key[{}]",key);
			lockObj.reduceReqNum();
			return null;
		}
	}

	/**
	 * 释放锁
	 * 线程执行完后,需要将锁释放.其他线程才能获取锁
	 * @param key 锁Key
	 */
	public static void unLock(String key) throws Exception {
		if(ObjUtils.isBlank(key)) return;
		LockObj lockObj = LockUtils.getLock(key);
//		修改锁状态为释放
		lockObj.getLock().unlock();
//		申请成功的次数,需要在释放时扣减
//		锁申请次数-1
		lockObj.reduceReqNum();
	}

	/**
	 * 获取 申请次数
	 * 锁对象记录了被申请的次数
	 * 线程申请时+1,线程释放时 or 申请失败时 -1
	 * @param key
	 * @return
	 * @throws Exception
	 */
	public static int getReqNum(String key) throws Exception {
		LockObj lockObj = LockUtils.getLock(key);
		return lockObj.getReqNum();
	}
}

/**
 * 封装 ReentrantLock 锁
 * 功能：
 * 尝试非阻塞地获取锁。如果锁当前没有被其他线程持有，那么当前线程可以获取到锁并返回true；
 * 如果锁已经被其他线程持有，那么当前线程不会被阻塞，而是直接返回false。
 * 每个ReentrantLock对象内部维护着一个锁状态。初始状态下，锁是未被占用的，状态为 0。
 * 当一个线程获取锁后，锁的状态会加 1
 * （因为是可重入锁，一个线程可以多次获取同一个锁）。
 * 每次线程调用unlock方法，锁的状态会减 1。当锁的状态再次变为 0 时，表示锁已经完全释放。
 *
 * 封装锁申请次数
 *
 */
class LockObj{
//	锁申请次数
//	不是上锁次数,上锁只会有一次
//	锁的状态只有锁定 or 释放
	private int lockReqNum;
	private Lock lock;
	
	LockObj(){
		super();
		lockReqNum=0;
		//通过构造函数ReentrantLock(true)来创建公平锁。
		//默认构造函数ReentrantLock()创建的是一个非公平锁。
		lock=new ReentrantLock();
	}
	
	public Lock getLock() {
		return lock;
	}
	
	public int getReqNum() {
		return lockReqNum;
	}
	
	public void addReqNum() {
		lockReqNum++;
	}
	
	public void reduceReqNum() {
		lockReqNum--;
		if(lockReqNum<0) lockReqNum=0;
	}
}
